// Dustin Escoffery
// Copyright 2010
#include "hook_window.h"
#include "hook_ddraw.h"
#include <windowsx.h>

#define IDM_ZOOM_NONE 100
#define IDM_ZOOM_HALF 101
#define IDM_ZOOM_DOUBLE 102
#define IDM_CLIP_CURSOR 103
#define IDM_ABOUT 104
static HWND bwWnd = NULL;
static WNDPROC lpfnWndProc = NULL;
static bool bClipCursor = false;

HWND GetMainWindow()
{
   return bwWnd;
}

void Aspectize(HWND hWnd, DWORD edge, LPRECT r)
{
   RECT cr, wr;
   GetClientRect(hWnd, &cr);
   GetWindowRect(hWnd, &wr);
   cr.left += r->left - wr.left;
   cr.top += r->top - wr.top;
   cr.right += r->right - wr.right;
   cr.bottom += r->bottom - wr.bottom;

   int width = cr.right - cr.left;
   int height = cr.bottom - cr.top;
   if (3 * width > 4 * height)
   {
      height = (3 * width) / 4 - height;
      width = 0;
   } else {
      width = (4 * height) / 3 - width;
      height = 0;
   }

   switch (edge)
   {
      case WMSZ_TOPLEFT:
      case WMSZ_TOPRIGHT:
         r->top -= height;
         break;
      default:
         r->bottom += height;
         break;
   }

   switch (edge)
   {
      case WMSZ_TOPLEFT:
      case WMSZ_BOTTOMLEFT:
         r->left -= width;
         break;
      default:
         r->right += width;
         break;
   }

   switch (edge)
   {
      case WMSZ_LEFT:
      case WMSZ_TOP:
      case WMSZ_RIGHT:
      case WMSZ_BOTTOM:
         r->left = wr.left;
         r->top = wr.top;
         r->right = wr.right;
         r->bottom = wr.bottom;
         break;
   }
}

// Custom Window Procedure
LRESULT CALLBACK WINAPI MyWindowProc(HWND hWnd, UINT uMsg,
   WPARAM wParam, LPARAM lParam)
{
   switch (uMsg)
   {
   // Process our custom commands
   case WM_SYSCOMMAND:
      if (wParam == IDM_ABOUT)
      {
         MessageBox(hWnd, "StarCraft Window\nUC Santa Cruz\nVersion 1.0.0",
            "About", MB_APPLMODAL | MB_ICONINFORMATION);
      }
      if (wParam == IDM_CLIP_CURSOR && !IsIconic(hWnd))
      {
         if (!bClipCursor)
         {
            RECT cr;
            POINT offset = {0,0};
            GetClientRect(hWnd, &cr);
            ClientToScreen(hWnd, &offset);
            OffsetRect(&cr, offset.x, offset.y);
            ClipCursor(&cr);
         }
         else ClipCursor(0);
         bClipCursor = !bClipCursor;
      }
      if (wParam >= IDM_ZOOM_NONE && wParam <= IDM_ZOOM_DOUBLE && !GetWindow(hWnd, GW_ENABLEDPOPUP))
      {
         // Select target client size
         RECT resize;
         GetClientRect(hWnd, &resize);
         switch (wParam)
         {
         case IDM_ZOOM_NONE: SetRect(&resize, 0, 0, 640, 480); break;
         case IDM_ZOOM_HALF: SetRect(&resize, 0, 0, resize.right / 2, resize.bottom / 2); break;
         case IDM_ZOOM_DOUBLE: SetRect(&resize, 0, 0, resize.right * 2, resize.bottom * 2); break;
         }
         // Restore First
         if (IsIconic(hWnd) || IsZoomed(hWnd))
         {
            CallWindowProc(lpfnWndProc, hWnd, WM_SYSCOMMAND, SC_RESTORE, 0);
            return MyWindowProc(hWnd, uMsg, wParam, lParam);
         // Resize Window to target client size
         } else {
            RECT wr;
            GetWindowRect(hWnd, &wr);
            AdjustWindowRectEx(&resize, GetWindowLong(hWnd, GWL_STYLE),
               (GetMenu(hWnd) != NULL), GetWindowLong(hWnd, GWL_EXSTYLE));
            // Dont shrink off the screen
            int right = wr.left + max(GetSystemMetrics(SM_CXMINTRACK), resize.right - resize.left);
            if (right < 64) OffsetRect(&wr, 64 - right, 0);
            // Resize it
            MoveWindow(hWnd, wr.left, wr.top, resize.right - resize.left,
               resize.bottom - resize.top, TRUE);
         }
      }
      break;
   // Our Hotkeys
   case WM_KEYUP:
      switch (wParam)
      {
      case VK_F5:
         MyWindowProc(hWnd, WM_SYSCOMMAND, IDM_CLIP_CURSOR, 0);
         break;
      }
      break;
   // Limit the window's minimum and maximum size
   case WM_GETMINMAXINFO:
      {
         RECT rc;
         LPMINMAXINFO i = (LPMINMAXINFO)lParam;
         SetRect(&rc, 0, 0, 160, 120);
         AdjustWindowRectEx(&rc, GetWindowLong(hWnd, GWL_STYLE),
            (GetMenu(hWnd) != NULL), GetWindowLong(hWnd, GWL_EXSTYLE));
         int ncX = rc.right - rc.left - 160;
         int ncY = rc.bottom - rc.top - 120;
         i->ptMinTrackSize.y = ncY + 3 * (i->ptMinTrackSize.x - ncX) / 4;
         int maxX = i->ptMaxTrackSize.x - ncX;
         int maxY = i->ptMaxTrackSize.y - ncY;
         if (3 * maxX > 4 * maxY)
         {
            i->ptMaxTrackSize.x = ncX + (4 * maxY) / 3;
         } else i->ptMaxTrackSize.y = ncY + (3 * maxX) / 4;
      }
      break;
   // Force the aspect ratio when resized externally
   case WM_SIZE:
      {
         int w = LOWORD(lParam);
         int h = HIWORD(lParam);
         int cmp = w * 3 - h * 4;
         // Ignore potential rounding error from local resizes
         if (abs(cmp) > 7)
         {
            RECT wr, resize;
            GetWindowRect(hWnd, &wr);
            if (cmp < 0) SetRect(&resize, 0, 0, w, (w * 3) / 4);
            else if (cmp > 0) SetRect(&resize, 0, 0, (h * 4) / 3, h);
            AdjustWindowRectEx(&resize, GetWindowLong(hWnd, GWL_STYLE),
               (GetMenu(hWnd) != NULL), GetWindowLong(hWnd, GWL_EXSTYLE));
            MoveWindow(hWnd, wr.left, wr.top, resize.right - resize.left,
               resize.bottom - resize.top, TRUE);
         }
      }
      break;
   // Draw DirectX to Screen (Very Important)
   case WM_PAINT:
      Redraw();
      break;
   // Force corner-dragging to resize 4:3
   case WM_SIZING:
      Aspectize(hWnd, wParam, (LPRECT)lParam);
      break;
   // Translate all mouse activity to original coordinates
   case WM_LBUTTONDBLCLK:
   case WM_LBUTTONDOWN:
   case WM_LBUTTONUP:
   case WM_RBUTTONDBLCLK:
   case WM_RBUTTONDOWN:
   case WM_RBUTTONUP:
   case WM_MOUSEMOVE:
      if (!GetWindow(hWnd, GW_ENABLEDPOPUP)) // if not bnet
      {
         while(ShowCursor(false) >= 0); // hide cursor in client
         RECT rcClient;
         GetClientRect(hWnd, &rcClient);
         if (rcClient.right > 0 && rcClient.bottom > 0) // transform
            lParam = MAKELPARAM(
               GET_X_LPARAM(lParam) * 640 / rcClient.right,
               GET_Y_LPARAM(lParam) * 480 / rcClient.bottom);
      } else while(ShowCursor(true) < 0); // otherwise show cursor
      break;
   // Show arrows on corner mouse-over
   case WM_NCMOUSEMOVE:
      {
         while(ShowCursor(true) < 0); // show cursor in non-client
         LPCSTR lpcCursor = IDC_ARROW;
         switch(wParam)
         {
         case HTBOTTOMLEFT:  case HTTOPRIGHT: lpcCursor = IDC_SIZENESW; break;
         case HTBOTTOMRIGHT: case HTTOPLEFT:  lpcCursor = IDC_SIZENWSE; break;
         }
         SetCursor(LoadCursor(NULL, lpcCursor));
      }
      break;
   // Modal during bnet dialogs
   case WM_ACTIVATE:
   case WM_MOUSEACTIVATE:
      {
         HWND dlg = GetWindow(hWnd, GW_ENABLEDPOPUP);
         if (dlg && wParam)
         {
            SetActiveWindow(dlg);
            return 4;
         }
      }
      break;
   // Remain active and restore cursor on lose focus
   case WM_ACTIVATEAPP:
      if (!wParam)
      {
         SetCursor(LoadCursor(NULL, IDC_ARROW));
         while(ShowCursor(true) < 0);
         if (bClipCursor) MyWindowProc(hWnd, WM_SYSCOMMAND, IDM_CLIP_CURSOR, 0);
         return DefWindowProc(hWnd, uMsg, wParam, lParam);
      }
      break;
   }
   return CallWindowProc(lpfnWndProc, hWnd, uMsg, wParam, lParam);
}

// Add custom entries to system menu
void CreateMySystemMenu(HWND hWnd)
{
   HMENU hMenu = GetSystemMenu(hWnd, FALSE);
   MENUITEMINFO mi;
   HMENU hFileMenu = CreateMenu();
   mi.cbSize      = sizeof(MENUITEMINFO);
   mi.fMask       = MIIM_FTYPE;
   mi.fType       = MFT_SEPARATOR;
   InsertMenuItem(hMenu, 5, TRUE, &mi);
   mi.fMask       = MIIM_FTYPE | MIIM_ID | MIIM_STRING;
   mi.fType       = MFT_STRING;
   mi.wID         = IDM_ZOOM_HALF;
   mi.dwTypeData  = "Half Size";
   mi.cch         = strlen("Half Size");
   InsertMenuItem(hMenu, 6, TRUE, &mi);
   mi.wID         = IDM_ZOOM_NONE;
   mi.dwTypeData  = "Normal Size";
   mi.cch         = strlen("Normal Size");
   InsertMenuItem(hMenu, 7, TRUE, &mi);
   mi.wID         = IDM_ZOOM_DOUBLE;
   mi.dwTypeData  = "Double Size";
   mi.cch         = strlen("Double Size");
   InsertMenuItem(hMenu, 8, TRUE, &mi);
   mi.fMask       = MIIM_FTYPE;
   mi.fType       = MFT_SEPARATOR;
   InsertMenuItem(hMenu, 9, TRUE, &mi);
   mi.fMask       = MIIM_FTYPE | MIIM_ID | MIIM_STRING;
   mi.fType       = MFT_STRING;
   mi.wID         = IDM_CLIP_CURSOR;
   mi.dwTypeData  = "Clip Cursor\tF5";
   mi.cch         = strlen("Clip Cursor\tF5");
   InsertMenuItem(hMenu, 10, TRUE, &mi);
   mi.fMask       = MIIM_FTYPE;
   mi.fType       = MFT_SEPARATOR;
   InsertMenuItem(hMenu, 11, TRUE, &mi);
   mi.fMask       = MIIM_FTYPE | MIIM_ID | MIIM_STRING;
   mi.fType       = MFT_STRING;
   mi.wID         = IDM_ABOUT;
   mi.dwTypeData  = "About";
   mi.cch         = strlen("About");
   InsertMenuItem(hMenu, 12, TRUE, &mi);
}

// Broodwar main window: add border style
HWND WINAPI MyCreateWindowEx(DWORD dwExStyle, LPCTSTR lpClassName,
   LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth,
   int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
   LPVOID lpParam)
{
   RECT rcWnd;
   dwStyle = WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME |
      WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
   SetRect(&rcWnd, 0, 0, 640, 480);
   AdjustWindowRectEx(&rcWnd, dwStyle, (hMenu != NULL), dwExStyle);
   bwWnd = CreateWindowEx(dwExStyle, lpClassName, lpWindowName, dwStyle,
      0, 0, rcWnd.right - rcWnd.left,
      rcWnd.bottom - rcWnd.top, hWndParent, hMenu, hInstance, lpParam);
   CreateMySystemMenu(bwWnd);
   return bwWnd;
}

// Hook main window procedure
ATOM WINAPI MyRegisterClassEx(CONST WNDCLASSEX *lpwcx)
{
   WNDCLASSEX* lpWC = (WNDCLASSEX*)lpwcx;
   lpfnWndProc = lpWC->lpfnWndProc;
   lpWC->lpfnWndProc = (WNDPROC)MyWindowProc;
   lpWC->style |= CS_CLASSDC | CS_HREDRAW | CS_VREDRAW;
   return RegisterClassEx(lpwcx);
}

// Allow Multiple Instances
HANDLE WINAPI MyCreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName)
{
   if (lpName) return NULL;
   return CreateEvent(lpEventAttributes, bManualReset, bInitialState, lpName);
}

// We will control cursor clipping
BOOL WINAPI MyClipCursor(const RECT *lpRect)
{
   if (!lpRect) return true;

   // Only allow interior clipping
   if (lpRect->right - lpRect->left < 640 || lpRect->bottom - lpRect->top < 480)
   {
      // Translate Coordinates
      RECT rcClient;
      POINT offset = {0,0};
      ClientToScreen(bwWnd, &offset);
      GetClientRect(bwWnd, &rcClient);
      LPRECT lprc = (LPRECT)lpRect;
      lprc->left = (lpRect->left * rcClient.right) / 640;
      lprc->right = (lpRect->right * rcClient.right) / 640;
      lprc->top = (lpRect->top * rcClient.bottom) / 480;
      lprc->bottom = (lpRect->bottom * rcClient.bottom) / 480;
      OffsetRect(lprc, offset.x, offset.y);
      ClipCursor(lpRect);
   }
   else
   {
      if (bClipCursor)
      {
         RECT cr;
         POINT offset = {0,0};
         GetClientRect(bwWnd, &cr);
         ClientToScreen(bwWnd, &offset);
         OffsetRect(&cr, offset.x, offset.y);
         ClipCursor(&cr);         
      } else ClipCursor(0);
   }
   return true;
}

// No Cursor Jumping
BOOL WINAPI MySetCursorPos(int X, int Y)
{
   return false;
}

// No Cursor Hiding
HCURSOR WINAPI MySetCursor(HCURSOR hCursor)
{
   return 0;
}

// Don't let BW slack off when minimized
BOOL WINAPI MyIsIconic(HWND hWnd)
{
   return FALSE;
}

// No Auto Scrolling
BOOL WINAPI MyGetCursorPos(LPPOINT lpPoint)
{
   // Allow Scrolling in Clip Mode
   if (bClipCursor)
   {
      BOOL ret = GetCursorPos(lpPoint);
      if (bwWnd)
      {
         // Translate Coordinates
         RECT rcClient;
         GetClientRect(bwWnd, &rcClient);
         ScreenToClient(bwWnd, lpPoint);
         lpPoint->x = (lpPoint->x == rcClient.right - 1) ? 639 :
            (lpPoint->x * 640) / rcClient.right;
         lpPoint->y = (lpPoint->y == rcClient.bottom - 1) ? 479 :
            (lpPoint->y * 480) / rcClient.bottom;
      }
      return ret;
   }
   lpPoint->x = 320;
   lpPoint->y = 240;
   return true;
}
